﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using BruteForce.Characters;
using BruteForce.Utilities;
using BruteForce.NewManagers;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace BruteForce.Screens.PlayingScreen.Map {

    [Serializable()]
    public class MapRow {
        public List<MapCell> Columns = new List<MapCell>();
    }

    public class TileMap
    {
        private static int pathSearchNumber = 1;
        public static HashSet<MapCell> HighlightBackUp = new HashSet<MapCell>();
        private readonly Texture2D mouseMap;
        private readonly Texture2D slopeMaps;
        public List<Character> CharactersInMap = new List<Character>();
        public int MapHeight = 100;
        public int MapWidth = 50;
        public List<MapRow> Rows = new List<MapRow>();
        public MapCell HumanInitialTower;
        public MapCell MonsterInitialTower;

        public List<MapCell> Towers = new List<MapCell>();
        private const int HighlightGenerateRangeFromTower = 3;

        public TileMap(Texture2D mouseMap, Texture2D slopeMap, int mapHeight, int mapWidth) {
            this.mouseMap = mouseMap;
            slopeMaps = slopeMap;

            this.MapHeight = mapHeight;
            this.MapWidth = mapWidth;

            for (int y = 0; y < MapHeight; y++) {
                var thisRow = new MapRow();
                for (int x = 0; x < MapWidth; x++) {
                    thisRow.Columns.Add(new MapCell(0, x, y));
                }
                Rows.Add(thisRow);
            }



            //newSquareLake(40, 10, 10);
            // End Create Sample Map Data
        }


        public void load(string fileName) {
            if (!File.Exists(fileName)) return;

            Rows = MapSerializer.DeSerializeObject(fileName);
            //CreateAndAlocateTower(20, 10, new Resources(10, 10, 10, 10), false);
            //CreateAndAlocateTower(20, 20, new Resources(10, 10, 10, 10), false);
            
            for (int i=0; i<Rows.Count; i++)
            {
                for (int j=0; j<Rows[i].Columns.Count; j++)
                {
                    var cell = Rows[i].Columns[j];
                    cell.Lowlight();
                    Random r = new Random();
                    if (cell.IsTower)
                    {
                        if (cell.HeightTiles.Count != 4)
                        {
                            cell.IsTower = false;
                        }
                        else
                        {
                            
                            if (cell.InitialyTowerBelongsTo == 1) {HumanInitialTower = cell; cell.Resources = new Resources(2,3,20,2);}
                            else if (cell.InitialyTowerBelongsTo == 2) { MonsterInitialTower = cell; cell.Resources = new Resources(2, 3, 20, 2); }
                            else cell.Resources = new Resources(r.Next(1) + 1, r.Next(2) + 1, 10, r.Next(1) + 1);
                            Towers.Add(cell);
                        }
                    }
                    
                }
            }

            if (HumanInitialTower == null) HumanInitialTower = Towers[0];
            if (MonsterInitialTower == null) MonsterInitialTower = Towers[1];

            Console.WriteLine("Towers: " + Towers.Count);
        }

        public void save(string fileName) {
            MapSerializer.SerializeObject(fileName, Rows);
        }

        public void CreateAndAlocateTower(int row, int column, Resources resources, bool updateRows) {
            if (updateRows) {
                Rows[row].Columns[column].AddHeightTile(-4);
                Rows[row].Columns[column].AddHeightTile(-3);
                Rows[row].Columns[column].AddHeightTile(-2);
                Rows[row].Columns[column].AddHeightTile(-1);

                Rows[row].Columns[column].IsTower = true;
                Rows[row].Columns[column].Resources = resources;
            }
            Towers.Add(Rows[row].Columns[column]);
        }


        public Point WorldToMapCell(Point worldPoint, out Point localPoint) {
            var mapCell = new Point(
                (worldPoint.X / mouseMap.Width),
                ((worldPoint.Y / mouseMap.Height)) * 2
                );

            int localPointX = worldPoint.X % mouseMap.Width;
            int localPointY = worldPoint.Y % mouseMap.Height;

            int dx = 0;
            int dy = 0;

            var myUint = new uint[1];

            if (new Rectangle(0, 0, mouseMap.Width, mouseMap.Height).Contains(localPointX, localPointY)) {
                mouseMap.GetData(0, new Rectangle(localPointX, localPointY, 1, 1), myUint, 0, 1);

                if (myUint[0] == 0xFF0000FF) // Red
                {
                    dx = -1;
                    dy = -1;
                    localPointX = localPointX + (mouseMap.Width / 2);
                    localPointY = localPointY + (mouseMap.Height / 2);
                }

                if (myUint[0] == 0xFF00FF00) // Green
                {
                    dx = -1;
                    localPointX = localPointX + (mouseMap.Width / 2);
                    dy = 1;
                    localPointY = localPointY - (mouseMap.Height / 2);
                }

                if (myUint[0] == 0xFF00FFFF) // Yellow
                {
                    dy = -1;
                    localPointX = localPointX - (mouseMap.Width / 2);
                    localPointY = localPointY + (mouseMap.Height / 2);
                }

                if (myUint[0] == 0xFFFF0000) // Blue
                {
                    dy = +1;
                    localPointX = localPointX - (mouseMap.Width / 2);
                    localPointY = localPointY - (mouseMap.Height / 2);
                }
            }

            mapCell.X += dx;
            mapCell.Y += dy - 2;

            localPoint = new Point(localPointX, localPointY);

            return mapCell;
        }

        public Point WorldToMapCell(Point worldPoint) {
            Point dummy;
            return WorldToMapCell(worldPoint, out dummy);
        }

        public Point WorldToMapCell(Vector2 worldPoint) {
            return WorldToMapCell(new Point((int)worldPoint.X, (int)worldPoint.Y));
        }

        public MapCell GetCellAtWorldPoint(Point worldPoint) {
            Point mapPoint = WorldToMapCell(worldPoint);
            return Rows[mapPoint.Y].Columns[mapPoint.X];
        }

        //public Point GetWorldPointOfMapCell(MapCell cell) {
        //    for (int i = 0; i < Rows.Count(); i++) {
        //        for (int j = 0; j < Rows[i].Columns.Count(); j++) {
        //            if (Rows[i].Columns[j] == cell) {
        //                Point worldPoint = new Point(j, i);
        //                return worldPoint;
        //            }
        //        }
        //    }
        //    return new Point(-1, -1);
        //}

        public MapCell GetCellAtWorldPoint(Vector2 worldPoint) {
            return GetCellAtWorldPoint(new Point((int)worldPoint.X, (int)worldPoint.Y));
        }

        public int GetSlopeMapHeight(Point localPixel, int slopeMap) {
            var texturePoint = new Point(slopeMap * mouseMap.Width + localPixel.X, localPixel.Y);

            var slopeColor = new Color[1];

            if (new Rectangle(0, 0, slopeMaps.Width, slopeMaps.Height).Contains(texturePoint.X, texturePoint.Y)) {
                slopeMaps.GetData(0, new Rectangle(texturePoint.X, texturePoint.Y, 1, 1), slopeColor, 0, 1);

                var offset = (int)(((255 - slopeColor[0].R) / 255f) * Tile.HeightTileOffset);

                return offset;
            }

            return 0;
        }

        public int GetSlopeHeightAtWorldPoint(Point worldPoint) {
            Point localPoint;
            Point mapPoint = WorldToMapCell(worldPoint, out localPoint);
            int slopeMap = Rows[mapPoint.Y].Columns[mapPoint.X].SlopeMap;

            return GetSlopeMapHeight(localPoint, slopeMap);
        }

        public int GetSlopeHeightAtWorldPoint(Vector2 worldPoint) {
            return GetSlopeHeightAtWorldPoint(new Point((int)worldPoint.X, (int)worldPoint.Y));
        }

        public int GetOverallHeight(Point worldPoint) {
            Point mapCellPoint = WorldToMapCell(worldPoint);
            int height = Rows[mapCellPoint.Y].Columns[mapCellPoint.X].HeightTiles.Count * Tile.HeightTileOffset;
            height += GetSlopeHeightAtWorldPoint(worldPoint);
            return height;
        }

        public int GetOverallHeight(Vector2 worldPoint) {
            return GetOverallHeight(new Point((int)worldPoint.X, (int)worldPoint.Y));
        }


        private void _HighlightWalkingRange(HashSet<Vector3> doneSet, int tileX, int tileY, int range, Color color)
        {
            if (range <= 0 || tileX < 0 || tileY < 0 || tileY > Rows.Count || tileX > Rows[0].Columns.Count)  return;
            doneSet.Add(new Vector3(tileX, tileY, range));
            var from = Rows[tileY].Columns[tileX];

            if (tileY % 2 == 0) {
                hlTile(from, doneSet, tileX, tileY, color, range - 1);
                hlTile(from, doneSet, tileX, tileY - 1, color, range - 1);
                hlTile(from, doneSet, tileX, tileY + 1, color, range - 1);
                hlTile(from, doneSet, tileX - 1, tileY + 1, color, range - 1);
                hlTile(from, doneSet, tileX - 1, tileY - 1, color, range - 1);
            } else {
                hlTile(from, doneSet, tileX, tileY, color, range - 1);
                hlTile(from, doneSet, tileX, tileY - 1, color, range - 1);
                hlTile(from, doneSet, tileX + 1, tileY - 1, color, range - 1);
                hlTile(from, doneSet, tileX, tileY + 1, color, range - 1);
                hlTile(from, doneSet, tileX + 1, tileY + 1, color, range - 1);
            }
        }

        private void hlTile(MapCell from, HashSet<Vector3> doneSet, int tileX, int tileY, Color color, int range) {
            if (tileX < 0 || tileY < 0 || tileY > Rows.Count || tileX > Rows[0].Columns.Count) return;
            var curcell = Rows[tileY].Columns[tileX];

            if (curcell.HeightTiles.Count > 0 && !curcell.IsTower) return;
            if (curcell.CharacterInCell != null && curcell.CharacterInCell != PlayerManager.Instance.CurrentPlayer.Property.CurrentCharacter)
                return;

            curcell.Highlight(color);
            HighlightBackUp.Add(curcell);

            
            if (curcell.bpathDist < range || curcell.pathSearchNumber != pathSearchNumber)
            {
                curcell.bpathDist = range;
                curcell.bpathBefore = from;
                curcell.pathSearchNumber = pathSearchNumber;
            }
            
            if (!doneSet.Contains(new Vector3(tileX,tileY,range)))
            {
                _HighlightWalkingRange(doneSet, tileX, tileY, range, color);
            }
        }



//        private void __HighlightWalkingRange(HashSet<Point> doneSet, int tileX, int tileY, int range, Color color) {
//            if (range <= 0) return;
//            //if (Rows[tileY].Columns[tileX].isHighlighted()) return;
//            if (doneSet.Contains(new Point(tileX, tileY))) return;
//            if (tileX < 0 || tileY < 0 || tileY > Rows.Count || tileX > Rows[0].Columns.Count) return;
//            Rows[tileY].Columns[tileX].Highlight(color);
//            HighlightBackUp.Add(Rows[tileY].Columns[tileX]);
//
//            //down
//            __HighlightWalkingRange(doneSet, tileX, tileY + 1, range - 1, color);
//            //up
//            __HighlightWalkingRange(doneSet, tileX, tileY - 1, range - 1, color);
//
//            //left and right
//            if (tileY % 2 == 0) {
//                __HighlightWalkingRange(doneSet, tileX - 1, tileY - 1, range - 1, color);
//                __HighlightWalkingRange(doneSet, tileX - 1, tileY + 1, range - 1, color);
//            } else {
//                __HighlightWalkingRange(doneSet, tileX + 1, tileY - 1, range - 1, color);
//                __HighlightWalkingRange(doneSet, tileX + 1, tileY + 1, range - 1, color);
//            }
//            doneSet.Add(new Point(tileX, tileY));
//        }
//

//        private void _FindBestWalkingPath(int sx, int sy, int ex, int ey, int range, List<Point> current,
//                                          List<Point> best) {
//            if (range == 0) return;
//            if (sx < 0 || sy < 0 || sy > Rows.Count || sx > Rows[0].Columns.Count) return;
//            current.Add(new Point(sx, sy));
//            if (sx == ex && sy == ey) {
//                if (best.Count == 0 || best.Count > current.Count) {
//                    best.Clear();
//                    best.AddRange(current);
//                }
//                return;
//            }
//
//            _FindBestWalkingPath(sx, sy + 1, ex, ey, range - 1, new List<Point>(current), best);
//            _FindBestWalkingPath(sx, sy - 1, ex, ey, range - 1, new List<Point>(current), best);
//
//            if (sy % 2 == 0) {
//                _FindBestWalkingPath(sx - 1, sy - 1, ex, ey, range - 1, new List<Point>(current), best);
//                _FindBestWalkingPath(sx - 1, sy + 1, ex, ey, range - 1, new List<Point>(current), best);
//            } else {
//                _FindBestWalkingPath(sx + 1, sy - 1, ex, ey, range - 1, new List<Point>(current), best);
//                _FindBestWalkingPath(sx + 1, sy + 1, ex, ey, range - 1, new List<Point>(current), best);
//            }
//        }

        private void _HighlightShootingRange(int tileX, int tileY, int range, Color color, Character shooter) {
            for (int dx = -1; dx < 2; dx++) {
                for (int dy = -1; dy < 2; dy++) {
                    for (int i = 0; i < range; i++) {
                        int x;
                        if (dx == 0 || dy == 0) {
                            x = (tileX + dx * i);
                        } else {
                            x = (int)(tileX + dx * (i + dx * (tileY % 2)) * 0.5);
                        }
                        int y = (tileY + dy * i * -1 * (dx == 0 || dy == 0 ? 2 : 1));

                        if (x < 0 || y < 0 || y >= Rows.Count || x >= Rows[0].Columns.Count) break;
                        var cell = Rows[y].Columns[x];
                        if (!(cell.shootable) && !cell.IsTower)
                        {
                            break;
                        } else if (cell.IsTower && cell.CharacterInCell != null && cell.CharacterInCell != NullCharacter.Instance && cell.CharacterInCell.BelongsToPlayer != PlayerManager.Instance.CurrentPlayer.Property.PlayerType)
                        {
                            Rows[y].Columns[x].Highlight(color);
                            HighlightBackUp.Add(Rows[y].Columns[x]);
                            break;
                        }

                        Rows[y].Columns[x].Highlight(color);
                        HighlightBackUp.Add(Rows[y].Columns[x]);

                        if (Rows[y].Columns[x].CharacterInCell != null &&
                            Rows[y].Columns[x].CharacterInCell != shooter) break;

                    }
                }
            }
        }

        public List<Point> FindBestWalkingPath(MapCell start, MapCell end, int radius) {
            var ans = new List<Point>();
            var cell = end;
            while (cell != start) {
                ans.Add(new Point((int)cell.Pos.X, (int)cell.Pos.Y));
                cell = cell.bpathBefore;
            }

            ans.Add(new Point((int)cell.Pos.X, (int)cell.Pos.Y));
            ans.Reverse();
            return ans;
        }

        private void _HighlightBombingRange(HashSet<Vector3> doneSet, int tileX, int tileY, int range, Color color) {
            if (range <= 0 || tileX < 0 || tileY < 0 || tileY > Rows.Count || tileX > Rows[0].Columns.Count) return;
            doneSet.Add(new Vector3(tileX, tileY, range));
            var from = Rows[tileY].Columns[tileX];

            if (tileY % 2 == 0) {
                hlBombTile(doneSet, tileX, tileY, color, range - 1);
                hlBombTile(doneSet, tileX, tileY - 1, color, range - 1);
                hlBombTile(doneSet, tileX, tileY + 1, color, range - 1);
                hlBombTile(doneSet, tileX - 1, tileY + 1, color, range - 1);
                hlBombTile(doneSet, tileX - 1, tileY - 1, color, range - 1);
            } else {
                hlBombTile(doneSet, tileX, tileY, color, range - 1);
                hlBombTile(doneSet, tileX, tileY - 1, color, range - 1);
                hlBombTile(doneSet, tileX + 1, tileY - 1, color, range - 1);
                hlBombTile(doneSet, tileX, tileY + 1, color, range - 1);
                hlBombTile(doneSet, tileX + 1, tileY + 1, color, range - 1);
            }
        }

        

        private void hlBombTile(HashSet<Vector3> doneSet, int tileX, int tileY, Color color, int range) {
            if (tileX < 0 || tileY < 0 || tileY >= Rows.Count || tileX >= Rows[0].Columns.Count) return;
            var curcell = Rows[tileY].Columns[tileX];

//            if (curcell.HeightTiles.Count > 0 && !curcell.IsTower) return;

            curcell.Highlight(color);
            HighlightBackUp.Add(curcell);


//            if (curcell.bpathDist < range || curcell.pathSearchNumber != pathSearchNumber) {
//                curcell.bpathDist = range;
//                curcell.bpathBefore = from;
//                curcell.pathSearchNumber = pathSearchNumber;
//            }

            if (!doneSet.Contains(new Vector3(tileX, tileY, range))) {
                _HighlightBombingRange(doneSet, tileX, tileY, range, color);
            }
        }

//        private void _HighlightBombingRange(int tileX, int tileY, int range, Color color) {
//            if (range == 0) return;
//            if (tileX < 0 || tileY < 0 || tileY > Rows.Count || tileX > Rows[0].Columns.Count) return;
//            Rows[tileY].Columns[tileX].Highlight(color);
//            HighlightBackUp.Add(Rows[tileY].Columns[tileX]);
//            _HighlightBombingRange(tileX, tileY + 1, range - 1, color);
//            _HighlightBombingRange(tileX, tileY - 1, range - 1, color);
//
//            if (tileY % 2 == 0) {
//                _HighlightBombingRange(tileX - 1, tileY - 1, range - 1, color);
//                _HighlightBombingRange(tileX - 1, tileY + 1, range - 1, color);
//            } else {
//                _HighlightBombingRange(tileX + 1, tileY - 1, range - 1, color);
//                _HighlightBombingRange(tileX + 1, tileY + 1, range - 1, color);
//            }
//        }

        

        /// <summary>
        /// HighlightWalkRange a zone
        /// </summary>
        /// <param name="tileX">the center tile x parameter</param>
        /// <param name="tileY">the center tile y parameter</param>
        /// <param name="color">the color to highligh to</param>
        /// <param name="radius">the highlight radius</param>
        public void HighlightWalkRange(int tileX, int tileY, Color color, int radius) {
            Lowlight();
            pathSearchNumber++;
            _HighlightWalkingRange(new HashSet<Vector3>(), tileX, tileY, radius, color);
        }


        /// <summary>
        /// HighlightBombRange a zone
        /// </summary>
        /// <param name="tileX">the center tile x parameter</param>
        /// <param name="tileY">the center tile y parameter</param>
        /// <param name="color">the color to highligh to</param>
        /// <param name="radius">the highlight radius</param>
        public void HighlightBombRange(int tileX, int tileY, Color color, int radius) {
            Lowlight();
            var toHighlight = new HashSet<MapCell>();
            _HighlightBombingRange(new HashSet<Vector3>(), tileX, tileY, radius, color);
        }


        public void HighlightGenerateRange(Color color) {
            Lowlight();
            foreach (MapCell mapCell in PlayerManager.Instance.CurrentPlayer.Property.Towers) {
                if (mapCell.CharacterInCell is Defender)
                {
                    _HighlightWalkingRange(new HashSet<Vector3>(), (int) mapCell.Pos.X, (int) mapCell.Pos.Y,
                                           HighlightGenerateRangeFromTower, color);
                    Rows[(int) mapCell.Pos.Y].Columns[(int) mapCell.Pos.X].Lowlight();
                }
            }
        }


        /// <summary>
        /// HighlightShootRange a zone
        /// </summary>
        /// <param name="tileX">the center tile x parameter</param>
        /// <param name="tileY">the center tile y parameter</param>
        /// <param name="color">the color to highligh to</param>
        /// <param name="radius">the highlight radius</param>
        public void HighlightShootRange(int tileX, int tileY, Color color, int radius, Character shooter) {
            Lowlight();
            var toHighlight = new HashSet<MapCell>();
            _HighlightShootingRange(tileX, tileY, radius, color, shooter);
        }

        /// <summary>
        /// remove the last highlighting
        /// </summary>
        public void Lowlight() {
            HighlightBackUp.ToList().ForEach(c => c.Lowlight());
            HighlightBackUp.Clear();
        }
    }
}